//*************************************************************************************
// File:     XGDESKEntity.cs
//*************************************************************************************
// Description: Clase abstracta de base que representa una entidad de simulacion y el
//              almacen estatico para elementos temporalmente inactivos. Se define un
//              delegado que es empleado para la emision de eventos sobre XGDESK.
//*************************************************************************************
// Classes:      XGDESKEntity : IEquatable<XGDESKEntity>, IDisposable
//*************************************************************************************
// Delegates:   void SendEventDelegate(ulong, ulong, double, iXGDESKEventArgs)
//*************************************************************************************
// Use:         La factoria de entidades debera pasarle al constructor los valores para
//              el tipo de objeto y la variante de instancia reservada, de ese modo, el
//              constructor obtiene un GUID valido para la entidad.
//              Las entidades pueden desactivarse con el metodo Deactivate(), el cual 
//              realiza las operaciones de desactivacion y coloca una referencia a la
//              instancia en el almacen estatico de elementos temporalmente inactivos.
//              Utilizamos el metodo estatico XGDESKEntity.ActivateEntity(ulong) para
//              retirar una entidad del citado almacen, marcandola como activa.
//              Empleamos SendEvent(ulong,double,iXGDESKEventArgs) para lanzar eventos
//              sobre XGDESK.
//              Deben sobrecargarse las funciones ProcessEvent(XGDESKEvent),
//              EventRevoked(XGDESKEvent), DoInitialEvents() y StopEntity() para
//              modelar el comportamiento de la entidad en la simulacion y el sistema.
//*************************************************************************************
// Author:      ManOguaR
//*************************************************************************************


using Xeralabs.Core.Entities.Internal;
using Xeralabs.Core.Events;
using Xeralabs.Core.Events.Handling;
using System;
using System.Collections.Generic;
using Xeralabs.Core.Utils.Internals;

namespace Xeralabs.Core.Entities
{
    /// <summary>
    /// Clase abstracta que representa una entidad de simulacion. Contiene un registro estatico temporal para elementos inactivos.
    /// </summary>
    public abstract class XGDESKEntity : IEquatable<XGDESKEntity>, IDisposable
    {
        #region Miembros de Clase Estatica (registro global de inactivos)
        private static Dictionary<ulong, XGDESKEntity> _inactiveInstances = null;

        static XGDESKEntity()
        {
            _inactiveInstances = new Dictionary<ulong, XGDESKEntity>();
        }

        private static bool SetInactive(XGDESKEntity entity)
        {
            if (entity._guid64 == 0)
            {
                //TODO: INVALID GUID64
                return false;
            }
            try
            {
                lock (_inactiveInstances)
                {
                    _inactiveInstances.Add(entity._guid64, entity);

                    if (entity._emmit != null)
                        entity._emmit = null;

                    return _inactiveInstances[entity._guid64]._inactive = true;
                }
            }
            catch (ArgumentException)
            {
                //TODO: YA EXISTE GUID64
                return ((entity.Equals(_inactiveInstances[entity._guid64])) && (_inactiveInstances[entity._guid64]._inactive));
            }
            catch (KeyNotFoundException)
            {
                return false;
            }
        }
        /// <summary>
        /// Devuelve la entidad identificada por el parametro <see cref="entityGUID"/>, marcada como activa,
        /// si esta se encuentra en el registro global de entidades inactivas.
        /// </summary>
        /// <param name="entityGUID">El identificador unico de 64 bits rlacionado con la entidad.</param>
        /// <returns>
        /// La entidad identificada por el parametro <see cref="entityGUID"/>, marcada como activa.
        /// Devuelve null si el identificador no se encuentra.
        /// </returns>
        internal static XGDESKEntity ActivateEntity(ulong entityGUID)
        {
            try
            {
                lock (_inactiveInstances)
                {
                    XGDESKEntity res = _inactiveInstances[entityGUID];
                    if (_inactiveInstances.Remove(entityGUID))
                    {
                        res._inactive = false;
                        return res;
                    }
                }
            }
            catch (KeyNotFoundException)
            { }
            return null;
        }
        /// <summary>
        /// Destruye una entidad inactiva. Si la entidad no se encuentra, no sucede nada.
        /// </summary>
        /// <param name="entityGUID">El identificador unico de 64 bits rlacionado con la entidad.</param>
        internal static void KillInactiveEntity(ulong entityGUID)
        {
            try
            {
                lock (_inactiveInstances)
                {
                    _inactiveInstances[entityGUID].Dispose();
                }
            }
            catch (KeyNotFoundException)
            { }
        }
        /// <summary>
        /// Devuelve una matriz unidimensional de GUIDs inactivos.
        /// </summary>
        /// <returns>Una matriz unidimensional con todos los GUIDs en el registro global de inactivos.</returns>
        public static ulong[] GetInactiveGUIDList()
        {
            ulong[] res = new ulong[_inactiveInstances.Keys.Count];
            int count = 0;
            foreach (ulong key in _inactiveInstances.Keys)
            {
                if (_inactiveInstances[key] != null)
                {
                    res[count] = key;
                    count++;

                    if (count >= res.Length)
                        break;
                }
            }
            return res;
        }
        #endregion

        #region Miembros de Clase Estatica (Administracion de Procesos)
        
        #endregion

        #region Identificador unico de 64bits
        private ulong _guid64 = 0;
        /// <summary>
        /// Devuelve el identificador unico de 64 bits para la entidad.
        /// </summary>
        /// <returns>Un valor entero sin signo de 64 bits que representa el identificador unico de la entidad.</returns>
        public ulong GetGUID() { return _guid64; }
        #endregion

        #region GDESK
        private event XGDESKEventDelegate _emmit = null;
        /// <summary>
        /// Establece el delegado para emitir eventos del kernel de simulacion.
        /// </summary>
        internal XGDESKEventDelegate Emmit { set { _emmit = value; } }
        /// <summary>
        /// Metodo para emitir un evento.
        /// </summary>
        /// <param name="reciverGUID">El identificador unico de 64 bits del receptor.</param>
        /// <param name="timeDelay">El retraso de tiempo del evento.</param>
        /// <param name="evtArgs">Un objeto que implementa la interfaz iXGDESKEventArgs y representa los argumentos del evento.</param>
        protected int SendInteraction(ulong reciverGUID, double timeDelay, iXGDESKEventArgs evtArgs)
        {
            if (_emmit != null)
                return this._emmit(this._guid64, reciverGUID, timeDelay, evtArgs);

            return -1;
        }
        /// <summary>
        /// Metodo para procesar una interaccion entrante.
        /// </summary>
        /// <param name="evt">La interaccion a procesar.</param>
        public abstract void ProcessInteraction(XGDESKEvent evt);
        /// <summary>
        /// Metodo para procesar una interaccion revocada (no se encontro el receptor).
        /// </summary>
        /// <param name="evt">La interaccion revocada a procesar.</param>
        public abstract void InteractionRevoked(XGDESKEvent evt);
        #endregion

        #region Administracion de Procesos
        private event ScheduleDelegate _schedule = null;
        /// <summary>
        /// Establece el delegado para ejecutar procesos sobre el organizador de la simulacion.
        /// </summary>
        internal ScheduleDelegate Schedule { set { _schedule = value; } }

        protected int SheduleProcess(TaskType type, double start, double rate, double duration, iTask task, object user)
        {
            if (_schedule != null)
                return this._schedule(type, start, rate, duration, task, user);

            return -1;
        }
        #endregion

        #region Constructores
        /// <summary>
        /// Constructor que genera un elemento inactivo por defecto. Usar junto con GetGUID()
        /// para obtener el identificador de la instancia.
        /// </summary>
        /// <param name="objectType">El tipo de objeto, asignado por la factoria.</param>
        /// <param name="reserved">El valor reservado, asignado por la factoria.</param>
        protected XGDESKEntity(byte objectType, byte reserved)
            : this(objectType, reserved, false) { }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="objectType">El tipo de objeto, asignado por la factoria.</param>
        /// <param name="reserved">El valor reservado, asignado por la factoria.</param>
        /// <param name="guid64">Parametro de salida que almacena el identificador unico de la instancia.</param>
        protected XGDESKEntity(byte objectType, byte reserved, out ulong guid64)
            : this(objectType, reserved, false, out guid64) { }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="objectType">El tipo de objeto, asignado por la factoria.</param>
        /// <param name="reserved">El valor reservado, asignado por la factoria.</param>
        /// <param name="active">Indica si el elemento es activo o inactivo.</param>
        /// <param name="guid64">Parametro de salida que almacena el identificador unico de la instancia.</param>
        protected XGDESKEntity(byte objectType, byte reserved, bool active, out ulong guid64)
            : this(objectType, reserved, active)
        {
            guid64 = this._guid64;
        }
        /// <summary>
        /// Constructor. Usar junto con GetGUID() para obtener el identificador de la instancia.
        /// </summary>
        /// <param name="objectType">El tipo de objeto, asignado por la factoria.</param>
        /// <param name="reserved">El valor reservado, asignado por la factoria.</param>
        /// <param name="active">Indica si el elemento es activo o inactivo.</param>
        protected XGDESKEntity(byte objectType, byte reserved, bool active)
        {
            this._guid64 = GUID64.GetNewGUID(objectType, reserved);
            if (!active)
                SetInactive(this);
            else
                _inactive = false;
        }
        #endregion

        #region Simulacion
        private bool _inactive;

        /// <summary>
        /// Metodo abstracto para lanzar los eventos de inicio de la entidad sobre la simulacion.
        /// </summary>
        public abstract void DoInitialEvents();
        /// <summary>
        /// Metodo abstracto para detener la simulacion de la entidad.
        /// </summary>
        public abstract void StopEntity(bool save);
        /// <summary>
        /// Desactiva la entidad y la almacena en el registro estatico temporal para elementos inactivos.
        /// </summary>
        /// <returns>True: la instancia fue almacenada con exito y marcada como inactiva, o ya se encontraba almacenada e inactiva; false: ya existe una instancia con el identificador de 64 bits proporcionado.</returns>
        public virtual bool Deactivate()
        {
            return XGDESKEntity.SetInactive(this);
        }
        #endregion

        #region Miembros de IEquatable<XGDESKEntity>
        /// <summary>
        /// Compara la instancia local con otro objeto XGDESKEntity.
        /// </summary>
        /// <param name="other">El objeto a comparar.</param>
        /// <returns>True: ambos objetos son identicos; false: los objetos no equivalen.</returns>
        public virtual bool Equals(XGDESKEntity other)
        {
            return this._guid64 == other._guid64;
        }
        #endregion

        #region Miembros de IDisposable
        /// <summary>
        /// Libera todos los recursos reservados por la instancia.
        /// </summary>
        /// <exception cref="InvalidOperationException">La instancia aun se encuentra activa.</exception>
        public virtual void Dispose()
        {
            if (_inactive)
            {
                lock (_inactiveInstances)
                {
                    _inactiveInstances.Remove(_guid64);
                }
                _guid64 = 0;
                _emmit = null;
            }
            else
                throw new InvalidOperationException();
        }
        #endregion
    }
}